package com.examples.gateway.helper.filter;

import com.examples.gateway.common.CheckState;
import com.examples.gateway.entity.SystemPermission;
import com.examples.gateway.helper.Constants;
import com.examples.gateway.helper.entity.ResponseContext;
import com.examples.gateway.service.ExamPermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 通过请求的路径匹配权限定义信息
 * @author Dai YaLu
 */
@Slf4j
public class GetPermissionFilter extends AbstractGlobalHelperFilter{

    @Autowired
    private ExamPermissionService permissionService;

    @Override
    public Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getRawPath();
        String method = request.getMethodValue().toLowerCase();
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        if (null != route) {
            String service = route.getId();
            // 获取完全匹配的权限
            SystemPermission permission = permissionService.getOne(service, method, path);
            if (null != permission) {
                exchange.getAttributes().put(Constants.EXAMPLES_GATEWAY_CHECK_PERMISSION, permission);
                return chain.filter(exchange);
            } else {
                // 获取该服务下该种请求类型的所有权限
                List<SystemPermission> airPermissionList = permissionService.getList(service, method);
                permission = bestMatchPermission(airPermissionList, path);
                if (null != permission) {
                    exchange.getAttributes().put(Constants.EXAMPLES_GATEWAY_CHECK_PERMISSION, permission);
                    return chain.filter(exchange);
                }
            }
        }
        ResponseContext resCtx = new ResponseContext();
        resCtx.setCheckState(CheckState.PERMISSION_MISMATCH);
        return handleError(exchange.getResponse(), resCtx);
    }

    @Override
    public int getOrder() {
        return 20;
    }

    /**
     * 最佳匹配的权限信息
     * @param list  所有的权限信息
     * @param path  请求的路径
     * @return  最佳匹配的权限路径
     */
    private SystemPermission bestMatchPermission(List<SystemPermission> list, String path) {
        if (null==list || list.isEmpty()) {
            return null;
        }
        AntPathMatcher matcher = new AntPathMatcher();
        return list
                .stream()
                .filter(p -> matcher.match(p.getPath(), path))
                .min(new AntPatternComparator(path))
                .orElse(null);
    }

    protected static class AntPatternComparator implements Comparator<SystemPermission> {

        private final String path;

        public AntPatternComparator(String path) {
            this.path = path;
        }

        @Override
        public int compare(SystemPermission p1, SystemPermission p2) {
            String pattern1 = p1.getPath();
            String pattern2 = p2.getPath();
            AntPatternComparator.PatternInfo info1 = new AntPatternComparator.PatternInfo(pattern1);
            AntPatternComparator.PatternInfo info2 = new AntPatternComparator.PatternInfo(pattern2);

            if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
                return 0;
            }
            else if (info1.isLeastSpecific()) {
                return 1;
            }
            else if (info2.isLeastSpecific()) {
                return -1;
            }

            boolean pattern1EqualsPath = pattern1.equals(this.path);
            boolean pattern2EqualsPath = pattern2.equals(this.path);
            if (pattern1EqualsPath && pattern2EqualsPath) {
                return 0;
            }
            else if (pattern1EqualsPath) {
                return -1;
            }
            else if (pattern2EqualsPath) {
                return 1;
            }

            if (info1.isPrefixPattern() && info2.isPrefixPattern()) {
                return info2.getLength() - info1.getLength();
            }
            else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
                return 1;
            }
            else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
                return -1;
            }

            if (info1.getTotalCount() != info2.getTotalCount()) {
                return info1.getTotalCount() - info2.getTotalCount();
            }

            if (info1.getLength() != info2.getLength()) {
                return info2.getLength() - info1.getLength();
            }

            if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
                return -1;
            }
            else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
                return 1;
            }

            if (info1.getUriVars() < info2.getUriVars()) {
                return -1;
            }
            else if (info2.getUriVars() < info1.getUriVars()) {
                return 1;
            }

            return 0;
        }


        private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
        /**
         * Value class that holds information about the pattern, e.g. number of
         * occurrences of "*", "**", and "{" pattern elements.
         */
        private static class PatternInfo {

            @Nullable
            private final String pattern;

            private int uriVars;

            private int singleWildcards;

            private int doubleWildcards;

            private boolean catchAllPattern;

            private boolean prefixPattern;

            @Nullable
            private Integer length;

            public PatternInfo(@Nullable String pattern) {
                this.pattern = pattern;
                if (this.pattern != null) {
                    initCounters();
                    this.catchAllPattern = this.pattern.equals("/**");
                    this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**");
                }
                if (this.uriVars == 0) {
                    this.length = (this.pattern != null ? this.pattern.length() : 0);
                }
            }

            protected void initCounters() {
                int pos = 0;
                if (this.pattern != null) {
                    while (pos < this.pattern.length()) {
                        if (this.pattern.charAt(pos) == '{') {
                            this.uriVars++;
                            pos++;
                        }
                        else if (this.pattern.charAt(pos) == '*') {
                            if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') {
                                this.doubleWildcards++;
                                pos += 2;
                            }
                            else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {
                                this.singleWildcards++;
                                pos++;
                            }
                            else {
                                pos++;
                            }
                        }
                        else {
                            pos++;
                        }
                    }
                }
            }

            public int getUriVars() {
                return this.uriVars;
            }

            public int getSingleWildcards() {
                return this.singleWildcards;
            }

            public int getDoubleWildcards() {
                return this.doubleWildcards;
            }

            public boolean isLeastSpecific() {
                return (this.pattern == null || this.catchAllPattern);
            }

            public boolean isPrefixPattern() {
                return this.prefixPattern;
            }

            public int getTotalCount() {
                return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards);
            }

            /**
             * Returns the length of the given pattern, where template variables are considered to be 1 long.
             */
            public int getLength() {
                if (this.length == null) {
                    this.length = (this.pattern != null ?
                            VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0);
                }
                return this.length;
            }
        }
    }
}
